/**
 * Copyright 2009-2020 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package me.itsoo.elasticbatis.builder.xml;

import me.itsoo.elasticbatis.builder.BaseBuilder;
import me.itsoo.elasticbatis.io.Resources;
import me.itsoo.elasticbatis.mapping.BoundSql;
import me.itsoo.elasticbatis.mapping.MappedStatement;
import me.itsoo.elasticbatis.mapping.ParameterMapping;
import me.itsoo.elasticbatis.mapping.SqlCommandType;
import me.itsoo.elasticbatis.parsing.XNode;
import me.itsoo.elasticbatis.parsing.XPathParser;
import me.itsoo.elasticbatis.session.Configuration;

import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class XMLMapperBuilder extends BaseBuilder {

    private final XPathParser parser;
    private final Map<String, XNode> sqlFragments;
    private final String resource;

    public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
        this(reader, configuration, resource, sqlFragments);
    }

    public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
                configuration, resource, sqlFragments);
    }

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
        this(inputStream, configuration, resource, sqlFragments);
    }

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
                configuration, resource, sqlFragments);
    }

    private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        super(configuration);

        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
    }

    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            configuration.addLoadedResource(resource);

            XNode context = parser.evalNode("/mapper");
            this.bindMapperForNamespace(context);
            this.parsePendingStatements(context);
        }
    }

    private void bindMapperForNamespace(XNode context) {
        String namespace = context.getStringAttribute("namespace");
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException e) {
                // ignore, bound type is not required
            }
            if (boundType != null && !configuration.hasMapper(boundType)) {
                // Spring may not know the real resource name, so we set a flag
                // to prevent loading again this resource from the mapper interface
                // look at MapperAnnotationBuilder#loadXmlResource
                configuration.addLoadedResource("namespace:" + namespace);
                configuration.addMapper(boundType);
            }
        }
    }

    private void parsePendingStatements(XNode context) {
        String namespace = context.getStringAttribute("namespace");
        List<XNode> list = context.evalNodes("select|insert|update|delete");
        for (XNode element : list) {
            String id = element.getStringAttribute("id");
            String parameterType = element.getStringAttribute("parameterType");
            String resultType = element.getStringAttribute("resultType");
            String sql = element.getStringBody("");

            List<ParameterMapping> parameterMappings = new ArrayList<>();

            // ? 匹配
            Map<Integer, String> parameter = new HashMap<>();
            Pattern pattern = Pattern.compile("(#\\{(.*?)})");
            Matcher matcher = pattern.matcher(sql);
            for (int i = 1; matcher.find(); i++) {
                String g1 = matcher.group(1);
                String g2 = matcher.group(2);
                parameter.put(i, g2);
                sql = sql.replace(g1, "?");
            }

            String statementId = namespace + "." + id;
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(element.getName().toUpperCase(Locale.ENGLISH));

            configuration.addMappedStatement(MappedStatement.builder()
                    .configuration(configuration)
                    .id(statementId)
                    .sqlCommandType(sqlCommandType)
                    .parameterType(parameterType)
                    .resultType(resultType)
                    .boundSql(new BoundSql(sql.trim(), parameterMappings, null))
                    .parameter(parameter)
                    .build());
        }
    }
}
